package controllers

import (
	"encoding/json"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
	"io/ioutil"
	"net/http"
	lmodel "payspace/models"
	"payspace/task"
	"strconv"
	"strings"
	"time"
)

type MainController struct {
	beego.Controller
}

func (this *MainController) URLMapping() {
	this.Mapping("getData", this.GetData)

}

const (
	//apiKey    = "77YK765AYPB4639NGNCQ6YEPZB2AT5TZUZ" // api key
	baseUrl = "https://api.tokenbalance.com/token/" // get balance api url
)

type params struct {
	Jsonrpc string              `json:"jsonrpc"`
	Method  string              `json:"method"`
	Params  []map[string]string `json:"params"`
	Id      string              `json:"id"`
}

/*type token struct {
	Symbol   string
	Protocol string
	Source   string
	Deny     bool
	Decimals float64
	IsMarket bool
}*/

// response err
/*func responseErr(c *MainController, msg string) {
	result := map[string]interface{}{"error": true, "message": msg}
	c.Data["json"] = result
	c.ServeJSON()
}*/

// response
func responseRet(c *MainController, ob params, r, err interface{}) {
	result := map[string]interface{}{"id": ob.Id, "jsonrpc": "2.0", "result": r, "error": err}
	c.Data["json"] = result
	c.ServeJSON()
}

// getBalance
func getBalance(c *MainController, ob params) {
	ethAddr := ob.Params[0]["ethAddr"]
	if ethAddr == "" {
		//responseErr(c, "missed eth address")
		err := make(map[string]interface{})
		err["message"] = "missed eth address"
		responseRet(c, ob, nil, err)
		return
	}
	// 写入数据库
	o := orm.NewOrm()
	eth := lmodel.EthAddr{EthAddr: ethAddr}
	err := o.Read(&eth, "EthAddr")
	if err != nil {
		eth.EthAddr = ethAddr
		_, err := o.Insert(&eth)
		if err != nil {
			logs.Error("insert eth addr failed:", err)
		}
	}

	tokens, err := lmodel.GetTokens(ethAddr)

	var urls []string
	if err != nil {
		// 默认请求地址
		url := baseUrl + "0xa74476443119A942dE498590Fe1f2454d7D4aC0d/" + ethAddr
		urls = append(urls, url)
	} else {
		// 构造请求地址列表
		for _, token := range tokens {
			conAddr := token.Protocol
			url := baseUrl + conAddr + "/" + ethAddr
			urls = append(urls, url)
		}
	}

	// call api to get balance
	var Tokens []map[string]interface{}
	var ethBalance string
	for _, url := range urls {
		i := 1
	RequestBalanceApi:
		resp, err := http.Get(url)
		if err != nil {
			// handle error
			i++
			logs.Error("call api failed: ", err)
			if i > 5 {
				continue
			}
			goto RequestBalanceApi
		}

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			//responseErr(c, "params error")
			err := make(map[string]interface{})
			err["message"] = "connect error, please try again later"
			responseRet(c, ob, nil, err)
			return
		}

		result := make(map[string]interface{})
		err = json.Unmarshal(body, &result)
		if err != nil {
			//responseErr(c, "params error")
			err := make(map[string]interface{})
			err["message"] = "connect error, please try again later"
			responseRet(c, ob, nil, err)
			return
		}

		token := make(map[string]interface{})

		ethBalance = result["eth_balance"].(string)
		token["token"] = result["symbol"].(string)
		token["balance"] = result["balance"].(string)

		Tokens = append(Tokens, token)
		resp.Body.Close()
	}

	// 构造响应结构体
	r := make(map[string]interface{})
	r["ethAddr"] = ethAddr
	r["eth_balance"] = ethBalance
	r["tokens"] = Tokens

	responseRet(c, ob, r, nil)
}

// getSupportedTokens
func getSupportedTokens(c *MainController, ob params) {
	var r []interface{}

	tokens, err := lmodel.GetSupportedTokens()
	if err != nil {
		//responseErr(c, "connect error, please check your network.")
		err := make(map[string]interface{})
		err["message"] = "connect error, please check your network"
		responseRet(c, ob, nil, err)
		return
	}

	for _, token := range tokens {
		res := make(map[string]interface{})
		res["protocol"] = token.Protocol
		res["symbol"] = token.Symbol
		res["source"] = token.Source
		res["deny"] = token.Deny
		res["decimals"] = token.Decimals
		res["isMarket"] = token.IsMarket

		r = append(r, res)
	}

	responseRet(c, ob, r, nil)
}

// getPrice
func getPrice(c *MainController, ob params) {
	currency := ob.Params[0]["currency"]
	if currency != "CNY" {
		//responseErr(c, "this currency is not supported")
		err := make(map[string]interface{})
		err["message"] = "this currency is not supported"
		responseRet(c, ob, nil, err)
		return
	}

	var tokens []map[string]interface{}
	for token, price := range task.PriceMap {
		res := make(map[string]interface{})
		res["token"] = token
		res["price"] = price
		tokens = append(tokens, res)
	}

	r := make(map[string]interface{})
	r["currency"] = currency
	r["tokens"] = tokens
	responseRet(c, ob, r, nil)
}

// favTokens
func favTokens(c *MainController, ob params) {
	ethAddr := ob.Params[0]["ethAddr"]
	tokensStr := ob.Params[0]["tokens"]

	o := orm.NewOrm()
	eth := lmodel.EthAddr{EthAddr: ethAddr}
	err := o.Read(&eth, "EthAddr")
	if err != nil {
		logs.Error("query eth addr failed:", err)
		//responseErr(c, "eth address is not exist")
		err := make(map[string]interface{})
		err["message"] = "eth address is not exist"
		responseRet(c, ob, nil, err)
		return
	}

	tokens := strings.Split(tokensStr, ",")
	m2m := o.QueryM2M(&eth, "Tokens") // QueryM2M对象

	if tokensStr == "" {
		// 清空关系表
		goto CancelFav
	}

	// 更新收藏关系表
	for _, symbol := range tokens {
		token := lmodel.Token{Symbol: symbol}
		err := o.Read(&token, "Symbol")
		if err != nil {
			logs.Error("query tokens failed:", err)
			//responseErr(c, "this token is not supported.")
			err := make(map[string]interface{})
			err["message"] = "this token is not supported"
			responseRet(c, ob, nil, err)
			return
		}

		if m2m.Exist(&token) {
			continue
		}

		_, err = m2m.Add(&token)
		if err != nil {
			logs.Error("add token failed:", err)
		}
	}

CancelFav:
	favTokens, err := lmodel.GetTokens(ethAddr) // 获取已收藏token
	if err != nil {
		logs.Error("query eth addr failed:", err)
		//responseErr(c, "no tokens are collected")
		err := make(map[string]interface{})
		err["message"] = "no tokens are collected"
		responseRet(c, ob, nil, err)
		return
	}

	delTag := make(map[string]int) // 取消收藏标签
	for _, favToken := range favTokens {
		delTag[favToken.Symbol] = 0
		for _, token := range tokens {
			if favToken.Symbol == token {
				delTag[favToken.Symbol] = 1
			}
		}
	}

	for Symbol, tag := range delTag {
		token := lmodel.Token{Symbol: Symbol}
		err := o.Read(&token, "Symbol")
		if err != nil {
			logs.Error("query tokens failed:", err)
			//responseErr(c, "this token is not supported.")
			err := make(map[string]interface{})
			err["message"] = "this token is not supported"
			responseRet(c, ob, nil, err)
			return
		}
		if tag == 0 && m2m.Exist(&token) {
			_, err = m2m.Remove(&token)
			if err != nil {
				logs.Error("remove token failed:", err)
			}
		}
	}

	r := make(map[string]interface{})
	r["ethAddr"] = ethAddr
	favTokens, err = lmodel.GetTokens(ethAddr)
	if err != nil {
		r["tokens"] = nil
	} else {
		var ret []string
		for _, token := range favTokens {
			ret = append(ret, token.Symbol)
		}
		r["tokens"] = ret
	}

	responseRet(c, ob, r, nil)
}

// getTransactions
func getTransactions(c *MainController, ob params) {
	var offset, pageIndex int
	var data []map[string]interface{} // 数据

	ethAddr := ob.Params[0]["ethAddr"]
	contract := ob.Params[0]["token"]
	if ob.Params[0]["pageIndex"] == "" {
		pageIndex = 1
		offset = 0
	} else {
		pageIndex, _ = strconv.Atoi(ob.Params[0]["pageIndex"])
		offset = (pageIndex - 1) * 20
	}

	transactions, err := lmodel.GetTransactions(ethAddr, contract, offset)
	if err != nil {
		r := make(map[string]interface{})
		r["data"] = nil
		r["pageIndex"] = pageIndex
		r["pageSize"] = 20
		responseRet(c, ob, r, nil)
		return
	}

	for _, tx := range transactions {
		transaction := make(map[string]interface{})

		transaction["blockNumber"] = tx.BlockNumber
		transaction["timeStamp"] = tx.TimeStamp
		transaction["hash"] = tx.Hash
		nonceInt, _ := strconv.ParseInt(tx.Nonce[2:], 16, 32)
		transaction["nonce"] = strconv.FormatInt(nonceInt, 10)
		transaction["blockHash"] = tx.BlockHash
		transaction["from"] = tx.From
		transaction["to"] = tx.To
		valueInt, _ := strconv.ParseInt(tx.Value[2:], 16, 64)
		transaction["value"] = strconv.FormatInt(valueInt, 10)
		gasInt, _ := strconv.ParseInt(tx.Gas[2:], 16, 64)
		transaction["gas"] = strconv.FormatInt(gasInt, 10)
		gasPriceInt, _ := strconv.ParseInt(tx.GasPrice[2:], 16, 64)
		transaction["gasPrice"] = strconv.FormatInt(gasPriceInt, 10)
		transaction["gasUsed"] = tx.GasUsed
		transaction["confirmations"] = tx.Confirmations
		status := tx.Status
		switch status {
		case "":
			transaction["status"] = "pending"
		case "-1":
			transaction["status"] = "dropped"
		case "1":
			transaction["status"] = "success"
		case "0":
			transaction["status"] = "failed"
		}

		if tx.From == ethAddr {
			transaction["txType"] = "send"
		} else {
			transaction["txType"] = "receive"
		}

		data = append(data, transaction)
	}

	r := make(map[string]interface{})
	r["data"] = data
	r["pageIndex"] = pageIndex
	r["pageSize"] = 20

	responseRet(c, ob, r, nil)
}

// notifyTransactionSubmitted
func notifyTransactionSubmitted(c *MainController, ob params) {
	var transaction lmodel.Transaction
	transaction.Hash = ob.Params[0]["hash"]
	transaction.Nonce = ob.Params[0]["nonce"]
	transaction.To = ob.Params[0]["to"]
	transaction.Value = ob.Params[0]["value"]
	transaction.GasPrice = ob.Params[0]["gasPrice"]
	transaction.Gas = ob.Params[0]["gas"]
	transaction.Input = ob.Params[0]["input"]
	transaction.From = ob.Params[0]["from"]
	transaction.Contract = ob.Params[0]["contract"]
	transaction.TimeStamp = strconv.FormatInt(time.Now().Unix(), 10)

	o := orm.NewOrm()
	// 判断数据是否存在
	t := lmodel.Transaction{Hash: ob.Params[0]["hash"]}
	err := o.Read(&t, "Hash")
	if err == nil {
		// 更新数据
		if _, err := o.Update(&transaction); err != nil {
			logs.Error("Update transaction failed: ", err)
		}
	} else {
		// 判断nonce是否相同
		var txs []*lmodel.Transaction
		o := orm.NewOrm()
		num, err := o.QueryTable("transaction").Filter("from", ob.Params[0]["from"]).Filter("nonce", ob.Params[0]["nonce"]).Filter("status", "").All(&txs)
		if err == nil || num > 0 {
			for _, tx := range txs {
				tx.Status = "-1" // tx被覆盖,状态置为dropped.
				o.Update(tx, "Status")
			}
		}
		// 新增数据
		if _, err := o.Insert(&transaction); err != nil {
			logs.Error("Add transaction failed: ", err)
		}
	}

	// 构造响应结构体
	r := make(map[string]interface{})
	r["txHash"] = transaction.Hash
	r["status"] = "pending"

	responseRet(c, ob, r, nil)
}

// getFavTokens
func getFavTokens(c *MainController, ob params) {
	var r []interface{}

	ethAddr := ob.Params[0]["ethAddr"]
	tokens, err := lmodel.GetTokens(ethAddr)
	if err != nil {
		logs.Error("query favorite tokens failed:", err)
		r = nil
	}

	for _, token := range tokens {
		res := make(map[string]interface{})
		res["protocol"] = token.Protocol
		res["symbol"] = token.Symbol
		res["source"] = token.Source
		res["deny"] = token.Deny
		res["decimals"] = token.Decimals
		res["isMarket"] = token.IsMarket

		r = append(r, res)
	}

	responseRet(c, ob, r, nil)
}

// @Title PaySpace rpc 2.0 api
// @Description 订阅邮箱 mail
// @Param   address      formData   string    true      "eth账户地址"
// @Success 200 {object}   controllers.Result        ""
// @Failure 400
// @Failure 404
// @router /getData [post]
func (c *MainController) GetData() {

	var ob params
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &ob) // Parse json data

	if err != nil {
		//responseErr(c, "params error")
		err := make(map[string]interface{})
		err["message"] = "params error"
		responseRet(c, ob, nil, err)
		return
	}

	// get request params
	method := ob.Method

	switch method {
	case "getBalance":
		getBalance(c, ob)

	case "getSupportedTokens":
		getSupportedTokens(c, ob)

	case "getPrice":
		getPrice(c, ob)

	case "favTokens":
		favTokens(c, ob)

	case "getTransactions":
		getTransactions(c, ob)

	case "notifyTransactionSubmitted":
		notifyTransactionSubmitted(c, ob)

	case "getFavTokens":
		getFavTokens(c, ob)

	default:
		//responseErr(c, "this method is not supported")
		err := make(map[string]interface{})
		err["message"] = "this method is not supported"
		responseRet(c, ob, nil, err)
		return
	}

	return
}
